package ca.uvic.group2.server;

import ca.uvic.group2.ServerFactory;
import ca.uvic.group2.api.*;
import ca.uvic.group2.api.exceptions.*;
import java.io.*;
import java.math.*;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.net.*;



public class DBServerImplStub extends UnicastRemoteObject implements DBServer, RemoteServer, StatsCollector {

    private String _serverName = "DBServer2";
    private Hashtable users = new Hashtable();
    private Hashtable buyTriggers = new Hashtable();
    private Hashtable sellTriggers = new Hashtable();
    private int triggerId = 0;
    private AuditLog logger;
    private String serverId = "DBServer";
    private ThreadPoolExecutor threadPool;
    private Stats myStats = new Stats(Stats.DB, "DBServer");

    public DBServerImplStub() throws RemoteException, CommException {
        super();
        logger = new ServerFactory().getAuditServer();

        threadPool = new ThreadPoolExecutor(10,  // Minimum 10 Threads in pool
                                            10,  // Maximum 10 Threads in pool
                                            1000, TimeUnit.SECONDS,  // Threads closed after 1000 seconds of inactivity
                                            new LinkedBlockingQueue(1000)); // Maximum Queue size

    }

    //
    // RemoteServer interface
    //
    private int _port;
    public void serverStart(int port) throws RemoteException, CommException {
        try {
            ServerFactory.serverStart(this, port, RMI_NAME);
            _port = port;
        } catch (Exception err) {
            err.printStackTrace();
            throw new CommException(err.getMessage());
        }
    }

    public void serverStop() throws RemoteException, CommException {
        try {
            System.out.println("DBServer received stop signal.  Cleaning up...");
            System.out.println("DBServer shutting down thread pool...");
            threadPool.shutdown();
            threadPool.awaitTermination(20, TimeUnit.SECONDS);

            System.out.println("DBServer closing all connections...");
            DBConnectionPool.getInstance().closeAllConnections();

            System.out.println("DBServer shutdown complete.");
        } catch (Exception err) {
            err.printStackTrace();
            throw new CommException(err.getMessage());
        }
    }


    public void setServerName(String name) throws RemoteException {
        _serverName = name;
        myStats.name = name;
    }

    public String getServerName() throws RemoteException {
        return _serverName;
    }

    public String getHostName() throws RemoteException {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException err) {
            return null;
        }
    }

    //
    //
    //


    public void initDatabase() throws CommException, RemoteException {
        //no interaction with the database needed for stub implementation
        return;

    }

    public int login(String username) throws LoginException, CommException, RemoteException {

        System.out.println("DB Login");


        // Otherwise, get the userinfo and userid.
        int userid = 1;
        UserInfo userInfo = new UserInfo(userid, username, new Money(100.0), new Money(0.0), new Money(0.0), null);
        userInfo.stocks.put("ABC", new StockRecord("ABC", 20));
        userInfo.stocks.put("DEF", new StockRecord("DEF", 10));

        // Store the userinfo and return the user id.
        users.put(new Integer(userid), userInfo);
        return userid;

    }

    private UserInfo findUserInfo(int userId) throws CommException {
        Integer key = new Integer(userId);
        if (users.containsKey(key)) {
            return (UserInfo)users.get(key);
        }
        throw new CommException("Userid not found");
    }

    public UserInfo getUserInfo(int userId) throws CommException {
        return new UserInfo(findUserInfo(userId));
    }

    public Receipt buy(int transId, int userId, PurchaseOrder order) throws CommException, RemoteException {
       System.out.println("DB Buy");
       String update_qry = "", stkSym = "";
       StockRecord stkRec = null;
       UserInfo userInfo = findUserInfo(userId);

       stkSym = order.quote.stock;

        //calc dollar new dollar account balance
       userInfo.balance = new Money(userInfo.balance.doubleValue() - order.total.doubleValue());


        //update the userinfo stock record
        stkRec = (StockRecord)userInfo.stocks.get(stkSym);
        if (stkRec == null) {
              //user doesn't own this stock add it
              userInfo.stocks.put(stkSym, new StockRecord(stkSym, order.amount));
        }
        else
        {
          //user owns the stock update the amount
          stkRec.amount += order.amount;
          userInfo.stocks.put(stkSym, stkRec);
        }

       // Otherwise, get the receipt
       Receipt r = new Receipt();
       r.confirmation = "xyz";
       r.order = new PurchaseOrder(order);
       r.balance = userInfo.balance;
       System.out.println("Buy complete");

       logger.accountRemove(serverId, transId, userInfo.username, order.total.doubleValue());

       // return the buy receipt
       return r;

    }

    public Receipt sell(int transId, int userId, PurchaseOrder order) throws CommException, RemoteException {
        System.out.println("sell: start");

        String stkSym = "", qry ="";
        int stkAmount = 0, stkBal = 0;
        BigDecimal bal = null;
        StockRecord stkRec = null;
        UserInfo userInfo = findUserInfo(userId);

        //update the stock and dollar balances in the db and update the userInfo to reflect this
        stkSym = order.quote.stock;
        stkAmount = order.amount;

        //calc new stock balance and dollar balance
        stkRec = (StockRecord)userInfo.stocks.get(stkSym);
        if (stkRec == null)
            throw new CommException("no stock record found");

        System.out.println("sell: cur num stock-" + stkRec.amount + "selling-" + stkAmount);
        stkBal = stkRec.amount - stkAmount;
        stkRec.amount = stkBal;

        bal = roundForDB(userInfo.balance.doubleValue());
        bal = bal.add(roundForDB(order.total.doubleValue()));
        userInfo.balance = new Money(bal.doubleValue());

        if (stkBal > 0) {
            userInfo.stocks.put(stkSym, stkRec);
        } else {
            userInfo.stocks.remove(stkSym);
        }

       // get the receipt
       Receipt r = new Receipt();
       r.confirmation = "sel";
       r.order = new PurchaseOrder(order);

       System.out.println("sell: end of sell");

       logger.accountAdd(serverId, transId, userInfo.username, order.total.doubleValue());

       return r;

    }

    public Receipt addFunds(int transId, int userId, float amount) throws CommException, RemoteException {

        System.out.println("add: start of add");

        UserInfo userInfo = findUserInfo(userId);

        userInfo.balance = new Money(userInfo.balance.doubleValue() + amount);
        // Otherwise, get the receipt
        Receipt r = new Receipt();
        r.confirmation = "aaa";
        r.order = new PurchaseOrder(PurchaseOrder.ADD_FUNDS);
        r.order.total = new Money(amount);
        r.order.amount = 1;
        r.balance = userInfo.balance;
        System.out.println("add: end of add");

        logger.accountAdd(serverId, transId, userInfo.username, r.order.total.doubleValue());

        return r;
    }


    /**
     * @return new balance
     */
    public int addBuyTrigger(int userId, Trigger trigger) throws CommException, RemoteException {
        System.out.println("DB addBuyTrigger");

        UserInfo userInfo = findUserInfo(userId);


        if (userInfo == null) {
            throw new CommException("No USer Info");
        }
        if (trigger == null) {
            throw new CommException("No Trigger");
        }
        userInfo.balance = userInfo.balance.subtract((double)trigger.amount);
        userInfo.triggerReserveBalance = userInfo.triggerReserveBalance.add((double)trigger.amount);
        trigger.id = 1;
        trigger.username = userInfo.username;

        // Otherwise, get result
        Trigger newTrigger = trigger;

        buyTriggers.put(new Integer(newTrigger.id), new Trigger(newTrigger));
        return newTrigger.id;
    }

    public void cancelBuyTrigger(int userId, int triggerId) throws CommException, RemoteException {

        BigDecimal bal;
        Integer id = new Integer(triggerId);
        Trigger trigger = (Trigger)buyTriggers.get(id);
        if (trigger == null) {
            throw new CommException("DBServer: Trigger not found");
        }
        buyTriggers.remove(id);

        //this should be a buy trigger so the dollar amount is the valid trigger amount
        if (Trigger.BUY != trigger.kind)
            throw new CommException("cancelBuyTrigger: Wrong trigger type!");

        UserInfo userInfo = findUserInfo(userId);
        userInfo.balance = userInfo.balance.add(trigger.amount);
        userInfo.triggerReserveBalance = userInfo.triggerReserveBalance.subtract(trigger.amount);

    }

    public int addSellTrigger(int userId, Trigger trigger) throws CommException, RemoteException {
        UserInfo userInfo = findUserInfo(userId);

        userInfo.balance = userInfo.balance.subtract((double)trigger.amount);
        userInfo.triggerReserveBalance = userInfo.triggerReserveBalance.add((double)trigger.amount);
        trigger.id = 1;
        trigger.username = userInfo.username;


        sellTriggers.put(new Integer(trigger.id), new Trigger(trigger));
        return trigger.id;
    }

    public void cancelSellTrigger(int userId, int triggerId) throws CommException, RemoteException {

        UserInfo userInfo = findUserInfo(userId);

        Integer id = new Integer(triggerId);
        Trigger trigger = (Trigger)sellTriggers.get(id);
        if (trigger == null) {
            throw new CommException("DBServer: Trigger not found");
        }

        StockRecord record = (StockRecord)userInfo.stocks.get(trigger.stock);
        if (record != null) {
            record.amount += trigger.amount;
            record.triggerReserveAmount -= trigger.amount;
        }
        buyTriggers.remove(id);

        //this should be a buy trigger so the dollar amount is the valid trigger amount
        if (Trigger.SELL != trigger.kind)
            throw new CommException("cancelSellTrigger: Wrong trigger type!");

    }


    //
    // StatsCollector Interface
    //


    public void beginRun() throws RemoteException {
        myStats.start();
    }

    public void endRun() throws RemoteException {
        myStats.stop();
    }

    public Stats getStats() throws RemoteException {
        return myStats.clone();
    }


    //*******************************************************
    protected static int getResultSetSize(ResultSet resultSet) {
        int size = -1;

        try {
            resultSet.last();
            size = resultSet.getRow();
            resultSet.beforeFirst();
        } catch (SQLException e) {
            return size;
        }

        return size;
    }


    //******************
    // private functions
    //******************
    private void addStats(long time, int readCount, int writeCount){
       myStats.addQuery(time);
                         myStats.incReadQueryCount(readCount);
                         myStats.incWriteQueryCount(writeCount);
    }


    private static BigDecimal roundForDB(double num) {
        double dbl = (double)num;
        BigDecimal bal = new BigDecimal(dbl);
        bal = bal.setScale(2, RoundingMode.HALF_EVEN);
        return bal;
    }

    private static BigDecimal roundForDB(float num) {
        double dbl = (double)num;
        BigDecimal bal = new BigDecimal(dbl);
        bal = bal.setScale(2, RoundingMode.HALF_EVEN);
        return bal;
    }


    //functions to execute and return the results of the given query string
    /*private int exec_update(String qry) throws SQLException {
        int result = 0;
        DBConnection2 db = new DBConnection2();
        try {
            result = db.execUpdate(qry);
        } catch (SQLException sqle) {
            throw new SQLException("exec_update failed" + sqle);
        }

        return result;
    }

    private ResultSet exec_qry(String qry) throws SQLException {
        ResultSet result = null;
        try {
            DBConnection2 db = new DBConnection2();
            System.out.println("exec_qry running: " + qry);
            result = db.execQuery(qry);
            System.out.println("exec_qry qry complete");
        } catch (SQLException sqle) {
            throw new SQLException("exec_qry failed" + sqle);
        }
        return result;
    }*/



}



